﻿#region Header File
//
// StopAndWait.cs - Stop And Wait ARQ Protocol
//
// Copyright (C) Javier Valcarce. BSD License
#endregion

#region Using Statements
using System;
using System.IO;
using System.Threading;
using System.Diagnostics;
#endregion

namespace Arq
{
    /// <summary>
    /// Stop And Wait ARQ Protocol.
    /// 
    /// WARNING: 
    /// This protocol is not connection
    /// This protocol is extremely simple nad have no method the explicitly close a "connection". To close 
    /// correctly the conection, wait a few miliseconds prior to finish application to let the ACK for the
    /// last packet be sent to the link's end point. If not, the end point will retry again and again the 
    /// retransmission of this last packet not acknowlegded and it will be the first packet you'll receive 
    /// the next time you execute you application.
    /// 
    /// </summary>
    public class StopAndWait : LinkProtocol
    {
        #region Members
        const ushort     Polinomial = 0x8005; // polynomial generator
        const int        HDRSZ      = 1;      // header size
        const int        CRCSZ      = 2;      // crc size         
        public const int MaxRetries = 5;
        public const int TxTimeout  = 50;

        // Types of frames
        enum Header : byte
        { 
            Ack0     = 0x00,
            Ack1     = 0x01,
            Data0    = 0x02,
            Data1    = 0x03,
        }

        MeanVar         rtavg;  // average retransmission
        DispatcherTimer timer;  // timer to control retransmissions
        Crc16           crc16;  // CRC 08-bit for Frame Error Control 
        //
        bool    txstate;        // tx   state
        bool    rxstate;        // rx   state
        bool    linksetup;        // link initialized
        bool    linkup;           // link up
        //
        int     pktTxQ;         // packet tx queue count
        int     retries;        // number of retries that was necesary for a sucessfull packet transmission
        byte[]  txdat;          // payload for Frame0 and Frame1 frame types        
        byte[]  rxfrm;          // rx frame 
        #endregion

        #region Contructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="tty">TTY device name (e.g. COM1, /dev/tty1, etc)</param>
        /// <param name="baudrate">Serial port baud rate (e.g. 19200)</param>
        public StopAndWait(Stream istream, Stream ostream) 
            : base(istream, ostream)
        {           
            crc16     = new Crc16(Polinomial);
            rtavg     = new MeanVar();
            pktTxQ    = 0;
            txstate   = false; // initial tx state
            rxstate   = false; // initial rx state
            linksetup = false;
            linkup    = false;

            // initially tx an empty data frame to syncronize receiver's state
            txdat   = new byte[0];
            retries = 0;

            // retransmission timer, strikes every TxTimeout milliseconds
            timer = new DispatcherTimer(Dispatcher, 0, TxTimeout,
                ProtocolTask, EventCode.TxTimeOut);

            // Fire!
            StartDispatherThread();
        }
        #endregion

        #region Properties
        public double AvrReTxPerPacket { get { return rtavg.Mean;      } }
        public double VarReTxPerPacket { get { return rtavg.Variance;  } }
        public int    MaxReTxPerPacket { get { return (int) rtavg.Max; } }
        /// <summary>
        /// When the maximum number os retransmissions is achieved this indicates that the channel
        /// is broken (link down). After this, the protocol enters in a blocking state and refuses 
        /// any new packet (SendPacket method). After this event, you must call TryAgain method to 
        /// try again data transmission over the link.
        /// </summary>
        public bool   IsLinkUp         { get { return linkup;          } }
        #endregion

        #region Events
        /// <summary>
        /// A new packet has arrived. Take it calling RecvPacket method.
        /// </summary>        
        public event EventHandler NewPacket;

        /// <summary>
        /// When the maximum number os retransmissions is achieved this indicates that the channel
        /// is broken (link down). After this, the protocol enters in a blocking state and refuses 
        /// any new packet (SendPacket method). After event, you must call TryAgain method to try 
        /// again the data transmission over the link.
        /// </summary>
        public event EventHandler LinkDown;
        public event EventHandler LinkUp;
        #endregion
        
        #region Protocol

        /// <summary>
        /// Nuts and Bolts of the protocol
        /// </summary>
        /// <param name="reason">Event</param>
        protected override void ProtocolTask(object reason)
        {
            switch ((EventCode) reason)
            {
                case EventCode.NewPacket:
                    pktTxQ++;
                    if (!linksetup)    return;                   
                    if (txdat != null) return;

                    TX_Pkt();
                    break;

                case EventCode.NewFrame:

                    GetFrame(out rxfrm);
                    Debug.Assert(rxfrm != null);
                    Debug.Assert(rxfrm.Length != 0);

                    // Check errors
                    // check nº 1: minimum frame size is HDRSZ + CRCSZ
                    // check nº 2: CRC 
                    if (rxfrm.Length < HDRSZ + CRCSZ)                     return;
                    if (!crc16.ValidFrame(rxfrm, 0, rxfrm.Length, false)) return;

                    // ----------------------------------------------------------------------------
                    // TX (acks)
                    // ----------------------------------------------------------------------------
                    if ((rxfrm[0] == (byte) Header.Ack0)  || (rxfrm[0] == (byte) Header.Ack1))
                        TX_Ack();
                    // ----------------------------------------------------------------------------
                    // RX (data and reset frames)
                    // ----------------------------------------------------------------------------                    
                    if ((rxfrm[0] == (byte) Header.Data0) || (rxfrm[0] == (byte) Header.Data1))
                        RX_Dat();
                    // ----------------------------------------------------------------------------
                    break;

                case EventCode.TxTimeOut:
                    TX_TimeOut();
                    break;
                default:
                    Debug.Assert(false);
                    break;
            }            
        }

        /// <summary>
        /// Get a packet from LinkProtocol and tx new packet
        /// </summary>
        void TX_Pkt()
        {
            Debug.Assert(txdat == null);
            GetPacket(out txdat);
            Debug.Assert(txdat != null);
            Debug.Assert(txdat.Length > 0);
            Debug.Assert(txdat.Length <= MTU);
            Debug.Assert(pktTxQ > 0);
            pktTxQ--;

            // send packet up for transmission
            byte[] f = BuildFrame((txstate) ? Header.Data1 : Header.Data0, txdat);
            PutFrame(ref f);

            // activate the retransmission timer
            retries = 0;
            timer.Change(TxTimeout, TxTimeout);
        }

        /// <summary>
        /// BUILD THE FRAME [ Header Payload CRC16 ] from PACKET stored at txpkt
        /// </summary>
        /// <param name="type"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        byte[] BuildFrame(Header type, byte[] data)
        {
            byte[] frame = new byte[HDRSZ + CRCSZ + 
                ((data != null) ? data.Length : 0 )];

            // header
            switch (type)
            {
                case Header.Data0: frame[0] = (byte)Header.Data0; break;
                case Header.Data1: frame[0] = (byte)Header.Data1; break;
                case Header.Ack0:  frame[0] = (byte)Header.Ack0;  break;
                case Header.Ack1:  frame[0] = (byte)Header.Ack1;  break;
            }

            // copy payload, if any
            if (data != null)
                Array.Copy(data, 0, frame, HDRSZ, data.Length);

            // crc-16
            crc16.AppendCode(frame, 0, frame.Length, false);
            return frame;
        }
        
        /// <summary>
        /// ACK   processing
        /// </summary>
        void TX_Ack()
        {
            // check nº 3: an ack must be have exactly 3 bytes
            if (rxfrm.Length != (HDRSZ + CRCSZ))
                return;

            // The rx sends the ack of the NEXT frame he wants to receive
            // not the ack of the received frame
            if (((!txstate) && (rxfrm[0] != (byte)Header.Ack1)) ||
                (( txstate) && (rxfrm[0] != (byte)Header.Ack0)))
                return;
            
            
            // link up
            if (!linkup)
            {
                linkup = true;
                if (LinkUp != null)
                    LinkUp.BeginInvoke(this, null, null, null);
            }


            // ok, the pkt in the tx bufffer has been acknowleged, anotate it 
            txdat = null;
            
            // change state
            txstate = !txstate;
            // compute the average retransmissions            
            rtavg.Compute(retries);
            retries = 0;

            // next packet, only if there are packet enqueued ready to be sent
            if (pktTxQ > 0)
                TX_Pkt();
            else
                timer.Change(TxTimeout, TxTimeout); // restart timer
        }

        /// <summary>
        /// FRAME processing
        /// </summary>
        void RX_Dat()
        {
            if (!linksetup)
            {
                // the first received packet is always accepted and used to sync the receivers
                // state with the transmmiter
                linksetup = true;
                rxstate = (rxfrm[0] == (byte)Header.Data1);
            }

            bool ok = false;
            ok = ok || (!rxstate && (rxfrm[0] == (byte)Header.Data0));
            ok = ok || ( rxstate && (rxfrm[0] == (byte)Header.Data1));

            if (ok) 
                rxstate = !rxstate;

            // send ACK thought the link
            byte[] f = BuildFrame((rxstate) ? Header.Ack1 : Header.Ack0, null);
            PutFrame(ref f);

            // deliver the data payload to application only if the data frame actually have data!
            if (ok && (rxfrm.Length > HDRSZ + CRCSZ))
            {
                PutPacket(rxfrm, HDRSZ, rxfrm.Length - (HDRSZ + CRCSZ));

                // raise event asynchronously
                if (NewPacket != null)
                    NewPacket.BeginInvoke(this, null, null, null);
            }
        }


        /// <summary>
        /// Service Routine for retransmission timer, resend a non-acknownleged packet or a reset
        /// frame for frame initialization.
        /// </summary>
        void TX_TimeOut()
        {
            // check for link down event
            if (linkup && (retries > MaxRetries))
            {
                linkup = false;
                if (LinkDown != null)
                    LinkDown.BeginInvoke(this, null, null, null);
            }

            // is there available data to re-transmmit?
            // if no data to tx, send an empty data frame: link periodic poolling
            if (txdat == null)
                txdat = new byte[0];

            // retransmmit
            byte[] f = BuildFrame((txstate) ? Header.Data1 : Header.Data0, txdat);
            PutFrame(ref f);
            retries++;
        }
        #endregion        

    }
}
